#include <linux/module.h>
#include <linux/fs.h>
#include <linux/platform_device.h>
#include <linux/fb.h>
#include <linux/dma-mapping.h>
#include <linux/clk.h>

/*  LCD T35参数设定 */
#define     LCD_WIDTH   240             /* LCD面板的行宽 */
#define     LCD_HEIGHT  320             /* LCD面板的列宽 */

#define     VSPW        1                /* 通过计算无效行数垂直同步脉冲宽度决定VSYNC脉冲的高电平宽度 */
#define     VBPD        1                /* 垂直同步周期后的无效行数 */
#define     LINEVAL     (LCD_HEIGHT-1)  /* LCD的垂直宽度-1 */
#define     VFPD        1                /* 垂直同步周期前的的无效行数 */

#define     CLKVAL      7              /* VCLK = HCLK / [(CLKVAL  + 1)  × 2] */

#define     HSPW        9                /* 通过计算VCLK的数水平同步脉冲宽度决定HSYNC脉冲的高电平宽度 */
#define     HBPD        19               /* 描述水平后沿为HSYNC的下降沿与有效数据的开始之间的VCLK周期数 */
#define     HOZVAL      (LCD_WIDTH-1)    /* LCD的水平宽度-1 */
#define     HFPD        9                /* 水平后沿为有效数据的结束与HSYNC的上升沿之间的VCLK周期数 */

/* 定义fb_info */
static struct fb_info *s3c_lcd;
/* 调色板数组,被fb_info->pseudo_palette调用 */
static u32 pseudo_palette[16];
/* 时钟 */
static struct clk *lcd_clk;

/* GPIO相关寄存器 */
static volatile unsigned long *gpcup;
static volatile unsigned long *gpccon;
static volatile unsigned long *gpdup;
static volatile unsigned long *gpdcon;
static volatile unsigned long *gpgcon;
static volatile unsigned long *gpgdat;
static volatile unsigned long *gpgup;

/* lcd相关寄存器 */
struct lcd_regs{
    unsigned long   lcdcon1;
    unsigned long    lcdcon2;
    unsigned long    lcdcon3;
    unsigned long    lcdcon4;
    unsigned long    lcdcon5;
    unsigned long    lcdsaddr1;
    unsigned long    lcdsaddr2;
    unsigned long    lcdsaddr3;
    unsigned long    redlut;
    unsigned long    greenlut;
    unsigned long    bluelut;
    unsigned long    reserved[9];
    unsigned long    dithmode;
    unsigned long    tpal;
    unsigned long    lcdintpnd;
    unsigned long    lcdsrcpnd;
    unsigned long    lcdintmsk;
    unsigned long    lpcsel;
};

static volatile struct lcd_regs* lcd_regs;

/*
 * 将内核单色使用bf表示
 * @param chan：单色 内核中的单色都是16位
 * @param bf：颜色位信息
 */
static inline unsigned int chan_to_field(unsigned int chan,
                     struct fb_bitfield *bf)
{
    chan &= 0xffff;
    chan >>= 16 - bf->length;
    return chan << bf->offset;
}

/*
 * 调色板操作函数
 *  @param regno: 调色板数组元素索引
 */
static int s3c2440fb_setcolreg(unsigned regno,
                   unsigned red, unsigned green, unsigned blue,
                   unsigned transp, struct fb_info *info)
{
    unsigned int val;
    //调色板数组不能大于15
    if (regno >= 16)
         return 1;

    /* 用red,green,blue三个颜色值构造出16色数据val */
    val  = chan_to_field(red,   &info->var.red);
    val |= chan_to_field(green, &info->var.green);
    val |= chan_to_field(blue,  &info->var.blue);

    /* 放到调色板数组中 */
    pseudo_palette[regno] = val;
    return 0;
}


#define samsung_device_dma_mask (*((u64[]) { DMA_BIT_MASK(32) }))

/*platform设备 */
static struct platform_device s3c_device_lcd = {
        .name           = "s3c2440-lcd",
        .id             = -1,
        .num_resources  = 0,
        .dev            = {
                .dma_mask               = &samsung_device_dma_mask,
                .coherent_dma_mask      = DMA_BIT_MASK(32),
        }
};

/*
 * s3c2440fb_map_video_memory():
 *      Allocates the DRAM memory for the frame buffer.  This buffer is
 *      remapped into a non-cached, non-buffered, memory region to
 *      allow palette and pixel writes to occur without flushing the
 *      cache.  Once this area is remapped, all virtual memory
 *      access to the video memory should occur at the new region.
 */
static int s3c2440fb_map_video_memory(struct fb_info *fbinfo)
{
        dma_addr_t map_dma;
        unsigned long map_size = PAGE_ALIGN(fbinfo->fix.smem_len);

        printk("map_video_memory(info=%px) map_size %u\n", fbinfo, map_size);

        // 第一个参数不能为空 否则会抛异常
        fbinfo->screen_base = dma_alloc_wc(&s3c_device_lcd.dev, map_size, &map_dma,
                                         GFP_KERNEL);

        if (fbinfo->screen_base) {
                /* prevent initial garbage on screen */
                printk("map_video_memory: clear %px:%08x\n",
                        fbinfo->screen_base, map_size);
                memset(fbinfo->screen_base, 0x00, map_size);

                fbinfo->fix.smem_start = map_dma;

                printk("map_video_memory: dma=%08lx cpu=%p size=%08x\n",
                        fbinfo->fix.smem_start, fbinfo->screen_base, map_size);
        }
        else
        {
          printk("map_video_memory fail\n");
        }

        return fbinfo->screen_base ? 0 : -ENOMEM;
}


/*
 * fb_info操作函数fbops
 */
static struct fb_ops s3c2440fb_ops = {
    .owner        = THIS_MODULE,
    .fb_setcolreg    = s3c2440fb_setcolreg,   // 调色板操作函数 设置调色板fb_info-> pseudo_palette
    .fb_fillrect    = cfb_fillrect,          // 填充矩形  函数在drivers/video/fbdev/core/cfbfillrect.c中定义
    .fb_copyarea    = cfb_copyarea,          // 复制数据  函数在drivers/video/fbdev/core/cfbcopyarea.c中定义
    .fb_imageblit    = cfb_imageblit,         // 绘制图形  函数在drivers/video/fbdev/core/cfbimgblt.c中定义
};


/*
 * lcd驱动模块入口
 */
static int lcd_init(void)
{
   int ret;
   printk("lcd device registered\n");

   /* 1. 初始化fb_info */
   s3c_lcd = framebuffer_alloc(0,NULL);
       if (!s3c_lcd)
       {
           return -ENOMEM;
       }

       printk("s3c_lcd=%px\n", s3c_lcd);

   /* 2. 设置fb_info */
   /* 2.1设置固定参数 */
    strcpy(s3c_lcd->fix.id, "mylcd");
    s3c_lcd->fix.smem_len        = LCD_WIDTH * LCD_HEIGHT * 2;   // framebuffer缓冲区的大小 每个像素两个字节
       s3c_lcd->fix.type            = FB_TYPE_PACKED_PIXELS;
    s3c_lcd->fix.type_aux        = 0;
    s3c_lcd->fix.xpanstep        = 0;
    s3c_lcd->fix.ypanstep        = 0;
    s3c_lcd->fix.ywrapstep       = 0;
    s3c_lcd->fix.accel           = FB_ACCEL_NONE;
    s3c_lcd->fix.visual          = FB_VISUAL_TRUECOLOR;      //真彩色
    s3c_lcd->fix.line_length     = LCD_WIDTH * 2;             // LCD一行所占字节数

    /* 2.2 设置可变参数 */
     s3c_lcd->var.xres            = LCD_WIDTH;         // 行分辨率
     s3c_lcd->var.yres            = LCD_HEIGHT;        // 列分辨率
     s3c_lcd->var.xres_virtual    = LCD_WIDTH;         // 行虚拟分辨率
     s3c_lcd->var.yres_virtual    = LCD_HEIGHT;        // 列虚拟分辨率
     s3c_lcd->var.xoffset         = 0;                 //虚拟到可见屏幕之间的行偏移
     s3c_lcd->var.yoffset         = 0;                 //虚拟到可见屏幕之间的行偏移
     s3c_lcd->var.bits_per_pixel  = 16;                // 每像素使用位数

     /* RGB:565 */
     s3c_lcd->var.red.offset      = 11;
     s3c_lcd->var.red.length      = 5;
     s3c_lcd->var.green.offset    = 5;
     s3c_lcd->var.green.length    = 6;
     s3c_lcd->var.blue.offset     = 0;
     s3c_lcd->var.blue.length     = 5;

     s3c_lcd->var.nonstd          = 0;
     s3c_lcd->var.activate        = FB_ACTIVATE_NOW;   // 使设置的值立即生效
     s3c_lcd->var.accel_flags     = 0;
     s3c_lcd->var.vmode           = FB_VMODE_NONINTERLACED;

     /* 2.3 设置操作函数 */
     s3c_lcd->fbops               = &s3c2440fb_ops;

     /* 2.4 其他设置 */
     s3c_lcd->flags               = FBINFO_FLAG_DEFAULT;
     s3c_lcd->pseudo_palette      = pseudo_palette;             // 保存调色板数组
     s3c_lcd->screen_size         = LCD_WIDTH * LCD_HEIGHT * 2;    // framebuffer缓冲区的大小

    // 时钟相关，获取lcd时钟
    lcd_clk = clk_get(NULL, "lcd");
    if (IS_ERR(lcd_clk)) {
        printk("failed to get lcd clock source\n");
        return PTR_ERR(lcd_clk);
    }

    clk_prepare_enable(lcd_clk);       // 时钟使能
    printk("got and enabled clock\n");

    /* 3.硬件相关操作 */
    /* 3.1 配置GPIO口用于LCD */
    gpcup = ioremap(0x56000028,4);
    gpccon = ioremap(0x56000020,4);
    gpdup = ioremap(0x56000038,4);
    gpdcon = ioremap(0x56000030,4);
    gpgcon = ioremap(0x56000060,12);
    gpgdat = gpgcon + 1;
    gpgup = gpgdat + 1;

    // GPIO管脚用于VD[7:0],LCDVF[2:0],VM,VFRAME,VLINE,VCLK,LEND
    *gpcup = 0xffffffff;
    *gpccon = 0xaaaaaaaa;
    // GPIO管脚用于VD[23:8]
    *gpdup = 0xffffffff;
    *gpdcon = 0xaaaaaaaa;

    /* Pull - up disable */
    *gpgup |= (1 << 4);
    // 设置GPG4引脚为LCD_PWREN模式
    *gpgcon |= (3 << 8);

    /* 3.2 根据LCD手册设置LCD控制器, 比如VCLK的频率等 */
    lcd_regs = ioremap(0x4D000000, sizeof(struct lcd_regs));
    printk("lcd_regs=%px map_size %u\n", lcd_regs, sizeof(struct lcd_regs));

    /*  [17:8]  CLKVAL
     *  [6:5]   PNRMODE；选择显示模式
     *                  00 = 4 位双扫描显示模式   01 = 4 位单扫描显示模式（STN）
     *                  10 = 8 位单扫描显示模式   11 = TFT LCD 面板
     *  [4:1]  BPPMODE  选择BPP（位每像素）模式    1100 = TFT 的16 BPP
     *  [0]    ENVID    LCD 视频输出和逻辑使能/禁止。
     *                  0 = 禁止视频输出和LCD 控制信号  1 = 允许视频输出和LCD 控制信号
     */
    lcd_regs->lcdcon1   = (CLKVAL<<8)| (3<<5) | (0xC<<1);              /* 16 bpp for TFT */

    /*  [31:24]   VBPD：帧同步信号的后肩
     *  [23:14]   LINEVAL：LCD面板的垂直尺寸
     *  [13:6]    VFPD：帧同步信号的前肩
     *  [5:0]     VSPW：同步信号的脉宽
     */
    lcd_regs->lcdcon2 = (VBPD<<24)|(LINEVAL<<14)|(VFPD<<6)|(VSPW);

    /* [25:19] HBPD: 行同步信号的后肩
     * [18:8] HOZVAL: LCD面板的水平尺寸
     * [7:0] HFPD: 行同步信号的前肩
     */
    lcd_regs->lcdcon3 = (HBPD<<19)|(HOZVAL<<8)|(HFPD);
    lcd_regs->lcdcon4 = (HSPW);

    /* [11] FRM565: 此位选择16 BPP 输出视频数据的格式   0 = 5:5:5:1 格式   1= 5:6:5 格式
     * [10] STN/TFT: 此位控制VCLK 有效沿的极性
     * [9]    INVVLINE: STN/TFT：此位表明VLINE/HSYNC 脉冲极性  0 = 正常 1 = 反转
     * [8]  INVVFRAME: STN/TFT：此位表明VFRAME/VSYNC 脉冲极性 0 = 正常 1 = 反转
     * VLINE/HSYNC 脉冲极性、VFRAME/VSYNC 脉冲极性反转(LCD型号决定)
     * [0]    HWSWP: STN/TFT：半字节交换控制位   0 = 交换禁止 1 = 交换使能
     */
    lcd_regs->lcdcon5 = ((1<<11) | (1<<10) | (1 << 9) | (1 << 8) | (1 << 0));

     /*  关闭PWREN信号输出 */
     lcd_regs->lcdcon1 &= ~(1<<0);
     /* 禁止PWREN信号 */
     lcd_regs->lcdcon5  &=~(1<<3);

     /* 第一位设置为1 选择输出分片率类型0:320 * 240  1:240*320 */
     lcd_regs->lpcsel = ((0xCE6) & ~7) | 1<<1;

    /* 3.3 分配显存(framebuffer), 并把地址告诉LCD控制器 */
    ret = s3c2440fb_map_video_memory(s3c_lcd);
    if (ret) {
        printk("failed to allocate video RAM: %d\n", ret);
        // todo 这里应该进行资源释放，我这里就不释放了
        return -ENOMEM;
    }

    /* [29:21] LCDBANK:存放帧缓冲起始地址的[30:22]
     * [20:0] LCDBASEU: 存放帧缓冲起始地址的[21:1]
     */
    lcd_regs->lcdsaddr1 = (s3c_lcd->fix.smem_start >> 1) & ~(3<<30);

    /* 存放帧结束地址[21:1] */
    lcd_regs->lcdsaddr2 = ((s3c_lcd->fix.smem_start + s3c_lcd->fix.smem_len) >> 1) & 0x1fffff;

    /* [21:11] OFFSIZE:表示虚拟屏偏移尺寸  即上一行最后像素点到下一行第一个像素点之间间隔多少个像素点
    *  [10:0] PAGEWIDTH:表示行的宽度（半字为单位 16bit）
    */
    lcd_regs->lcdsaddr3 = LCD_WIDTH & 0x3ff;

    /* 4.1 开启LCD */
    /* 控制LCDCON5允许PWREN信号 */
    lcd_regs->lcdcon5 |= (1 << 3);
    /* 控制LCDCON1 LCD使能 */
    lcd_regs->lcdcon1 |= (1<<0);
    /* 输出高电平, 使能背光 */
    *gpgdat |= 1<<4;

    printk("lcdcon[1] = 0x%08lx\n", lcd_regs->lcdcon1);
    printk("lcdcon[2] = 0x%08lx\n", lcd_regs->lcdcon2);
    printk("lcdcon[3] = 0x%08lx\n", lcd_regs->lcdcon3);
    printk("lcdcon[4] = 0x%08lx\n", lcd_regs->lcdcon4);
    printk("lcdcon[5] = 0x%08lx\n", lcd_regs->lcdcon5);
    printk("lcdsaddr[1]= 0x%08lx\n", lcd_regs->lcdsaddr1);
    printk("lcdsaddr[2]= 0x%08lx\n", lcd_regs->lcdsaddr2);
    printk("lcdsaddr[3]= 0x%08lx\n", lcd_regs->lcdsaddr3);

    /* 4.2 注册 */
    register_framebuffer(s3c_lcd);

    return 0;
}

/*
 * lcd驱动模块出口
 */
static void __exit lcd_exit(void)
{
    printk("lcd device unregistered\n");

    unregister_framebuffer(s3c_lcd);
     /* 禁止LCD使能 */
    lcd_regs->lcdcon1 &= ~(1<<0);
    /* 关闭背光 */
    *gpgdat &= ~(1<<4);
    dma_free_wc(&s3c_device_lcd.dev, s3c_lcd->fix.smem_len, s3c_lcd->screen_base, s3c_lcd->fix.smem_start);
    iounmap(lcd_regs);
    iounmap(gpcup);
    iounmap(gpccon);
    iounmap(gpdup);
    iounmap(gpdcon);
    iounmap(gpgcon);
    framebuffer_release(s3c_lcd);
}

module_init(lcd_init);
module_exit(lcd_exit);
MODULE_LICENSE("GPL");